﻿

#region Using Statements

using UnityEngine;
using System.Collections.Generic;
using System.Collections;

#endregion


/// <summary>
/// 
/// </summary>
public static class Util
{

    #region Members


    #endregion


    #region Random Vectors

    /// <summary>
    /// 
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public static Vector4 RandomVecPlusMinus(Vector4 range)
    {
        return new Vector4(
            Random.Range(-range.x, range.x),
            Random.Range(-range.y, range.y),
            Random.Range(-range.z, range.z),
            Random.Range(-range.w, range.w));
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="vec"></param>
    /// <returns></returns>
    public static Vector4 RandomInRange(this Vector4 vec) { return RandomVecPlusMinus(vec); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public static Vector3 RandomVecPlusMinus(Vector3 range)
    {
        return new Vector3(
            Random.Range(-range.x, range.x),
            Random.Range(-range.y, range.y),
            Random.Range(-range.z, range.z));
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="vec"></param>
    /// <returns></returns>
    public static Vector3 RandomInRange(this Vector3 vec) { return RandomVecPlusMinus(vec); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public static Vector2 RandomVecPlusMinus(Vector2 range)
    {
        return new Vector2(
            Random.Range(-range.x, range.x),
            Random.Range(-range.y, range.y));
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="vec"></param>
    /// <returns></returns>
    public static Vector2 RandomInRange(this Vector2 vec) { return RandomVecPlusMinus(vec); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public static float RandomFloatPlusMinus(float range) { return Random.Range(-range, range); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="fl"></param>
    /// <returns></returns>
    public static float RandomInRange(this float fl) { return RandomFloatPlusMinus(fl); }

    #endregion


    #region Normal Distribution

    /// <summary>
    /// 
    /// </summary>
    public static int DEFAULT_NORMAL_ITERS = 16;


    /// <summary>
    /// 
    /// </summary>
    /// <param name="mean"></param>
    /// <param name="stdDev"></param>
    /// <returns></returns>
    public static float RandomNormal(float mean, float stdDev)
    {
        return RandomNormal(mean, stdDev, DEFAULT_NORMAL_ITERS);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="mean"></param>
    /// <param name="stdDev"></param>
    /// <param name="iters"></param>
    /// <returns></returns>
    public static float RandomNormal(float mean, float stdDev, int iters)
    {
        float result = 0;

        for (int i = 0; i < iters; i++)
            result += Random.Range(-1f, 1f);

        result *= stdDev * 2;
        result += mean;

        return result;
    }

    #endregion


    #region List Extensions

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param attribute="index"></param>
    public static void UnorderedRemove<T>(this List<T> list, int index)
    {
        list[index] = list[list.Count - 1];
        list.RemoveAt(list.Count - 1);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="obj"></param>
    public static void UnorderedRemove<T>(this List<T> list, T val)
    {
        for (int i = 0; i < list.Count; i++)
        {
            if (ReferenceEquals(list[i], val))
            {
                UnorderedRemove(list, i);
                return;
            }
        }
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="index1"></param>
    /// <param name="index2"></param>
    public static void Swap<T>(this List<T> list, int index1, int index2)
    {
        T temp = list[index1];
        list[index1] = list[index2];
        list[index2] = temp;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="index"></param>
    public static void SwapToFront<T>(this List<T> list, int index)
    {
        Swap(list, index, 0);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    public static T RandomItem<T>(this List<T> list)
    {
        return list[Random.Range(0, list.Count)];
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="array"></param>
    /// <returns></returns>
    public static T RandomItem<T>(this T[] array)
    {
        return array[Random.Range(0, array.Length)];
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="startIndex"></param>
    /// <param name="endIndex"></param>
    /// <returns></returns>
    public static T RandomItem<T>(this List<T> list, int startIndex, int endIndex)
    {
        return list[Random.Range(startIndex, endIndex)];
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="array"></param>
    /// <param name="startIndex"></param>
    /// <param name="endIndex"></param>
    /// <returns></returns>
    public static T RandomItem<T>(this T[] array, int startIndex, int endIndex)
    {
        return array[Random.Range(startIndex, endIndex)];
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="K"></typeparam>
    /// <param name="list"></param>
    /// <param name="other"></param>
    public static void AddAll<T, K>(this List<T> list, ICollection<K> other) 
        where K : T
    {
        foreach (K k in other)
            list.Add(k);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="other"></param>
    public static void AddAll<T>(this List<T> list, params ICollection[] p) 
        where T : class
    {
        foreach (ICollection c in p)
            foreach (System.Object obj in c)
                if (obj is T)
                    list.Add(obj as T);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="k"></param>
    /// <param name="j"></param>
    /// <returns></returns>
    public static List<T> Merge<T>(params ICollection[] p) 
        where T : class
    {
        List<T> result = new List<T>();
        foreach (ICollection c in p)
            foreach (System.Object obj in c)
                if (obj is T)
                    result.Add(obj as T);

        return result;
    }

    #endregion


    #region Percentage Chance

    /// <summary>
    /// 
    /// </summary>
    /// <param name="chance"></param>
    /// <returns></returns>
    public static bool Roll(float chance) { return Random.Range(0f, 1f) < chance; }

    #endregion


    #region Vectors

    /// <summary>
    /// 
    /// </summary>
    /// <param name="t"></param>
    /// <param name="local"></param>
    /// <param name="global"></param>
    /// <returns></returns>
    public static Vector3 Offset(this Transform t, Vector3 local, Vector3 global)
    {
        return t.position + global + t.right * local.x + t.up * local.y + t.forward * local.z;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="t"></param>
    /// <param name="local"></param>
    /// <param name="global"></param>
    /// <returns></returns>
    public static Vector3 Offset(this Component t, Vector3 local, Vector3 global)
    {
        return t.transform.position + global + t.transform.right * local.x + t.transform.up * local.y + t.transform.forward * local.z;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="v"></param>
    /// <param name="val"></param>
    /// <returns></returns>
    public static Vector3 SetX(Vector3 v, float val) 
    { 
        Vector3 result = new Vector3(val, v.y, v.z);
        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="v"></param>
    /// <param name="val"></param>
    /// <returns></returns>
    public static Vector3 SetY(Vector3 v, float val)
    {
        Vector3 result = new Vector3(v.x, val, v.z);
        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="v"></param>
    /// <param name="val"></param>
    /// <returns></returns>
    public static Vector3 SetZ(Vector3 v, float val)
    {
        Vector3 result = new Vector3(v.x, v.y, val);
        return result;
    }

    #endregion


    #region Mesh Tangents

    /// <summary>
    /// 
    /// </summary>
    /// <param name="mesh"></param>
    /// <returns></returns>
    public static Vector4[] GenMeshTangents(Mesh mesh)
    {
        //speed up math by copying the mesh arrays
        int[] triangles = mesh.triangles;
        Vector3[] vertices = mesh.vertices;
        Vector2[] uv = mesh.uv;
        Vector3[] normals = mesh.normals;

        //variable definitions
        int triangleCount = triangles.Length;
        int vertexCount = vertices.Length;

        Vector3[] tan1 = new Vector3[vertexCount];
        Vector3[] tan2 = new Vector3[vertexCount];

        Vector4[] tangents = new Vector4[vertexCount];

        for (long a = 0; a < triangleCount; a += 3)
        {
            long i1 = triangles[a + 0];
            long i2 = triangles[a + 1];
            long i3 = triangles[a + 2];

            Vector3 v1 = vertices[i1];
            Vector3 v2 = vertices[i2];
            Vector3 v3 = vertices[i3];

            Vector2 w1 = uv[i1];
            Vector2 w2 = uv[i2];
            Vector2 w3 = uv[i3];

            float x1 = v2.x - v1.x;
            float x2 = v3.x - v1.x;
            float y1 = v2.y - v1.y;
            float y2 = v3.y - v1.y;
            float z1 = v2.z - v1.z;
            float z2 = v3.z - v1.z;

            float s1 = w2.x - w1.x;
            float s2 = w3.x - w1.x;
            float t1 = w2.y - w1.y;
            float t2 = w3.y - w1.y;

            float r = 1.0f / (s1 * t2 - s2 * t1);

            Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
            Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);

            tan1[i1] += sdir;
            tan1[i2] += sdir;
            tan1[i3] += sdir;

            tan2[i1] += tdir;
            tan2[i2] += tdir;
            tan2[i3] += tdir;
        }


        for (long a = 0; a < vertexCount; ++a)
        {
            Vector3 n = normals[a];
            Vector3 t = tan1[a];
            Vector3.OrthoNormalize(ref n, ref t);

            tangents[a].x = t.x;
            tangents[a].y = t.y;
            tangents[a].z = t.z;

            tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
        }

        return tangents;
    }

    #endregion


    #region App Status

    /// <summary>
    /// 
    /// </summary>
    public static bool isDesigner { get { return Application.isEditor && !Application.isPlaying; } }

    #endregion


    #region Standardised Temporal Exponents

    /// <summary>
    /// 
    /// </summary>
    /// <param name="exp"></param>
    /// <returns></returns>
    public static float TemporalExp(float exp)
    {
        return 1.0f - Mathf.Pow(1.0f - exp, Time.deltaTime);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="dampingPerSecond"></param>
    /// <returns></returns>
    public static float DampingCoefficient(float dampingPerSecond)
    {
        return Mathf.Pow(1.0f - dampingPerSecond, Time.deltaTime);
    }

    #endregion


    #region Normalize Angles

    /// <summary>
    /// 
    /// </summary>
    /// <param name="inp"></param>
    /// <returns></returns>
    public static float RadianDistance(float inp)
    {
        while (inp < 0)
            inp += Mathf.PI * 2.0f;
        while (inp >= Mathf.PI * 2.0f)
            inp -= Mathf.PI * 2.0f;
        if (inp > Mathf.PI)
            inp = Mathf.PI * 2 - inp;

        return inp;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="inp"></param>
    /// <returns></returns>
    public static float DegreeDistance(float inp)
    {
        while (inp < 0)
            inp += 360.0f;
        while (inp >= 360.0f)
            inp -= 360.0f;
        if (inp > 180.0f)
            inp = 360.0f - inp;

        return inp;
    }

    #endregion


    #region Get Component in Parent

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="xf"></param>
    /// <param name="includeSelf"></param>
    /// <returns></returns>
    public static T GetComponentInParent<T>(this Component c, bool includeSelf = true)
        where T : Component
    {
        T result = null;
        Transform xf = c.transform;

        if (!includeSelf)
            xf = xf.parent;

        while (result == null && xf != null)
        {
            result = (T)xf.GetComponent(typeof(T));
            xf = xf.parent;
        }

        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="xf"></param>
    /// <param name="T"></param>
    /// <param name="includeSelf"></param>
    /// <returns></returns>
    public static Component GetComponentInParent(this Transform xf, System.Type T, bool includeSelf = true)
    {
        Component result = null;

        if (!includeSelf)
            xf = xf.parent;

        while (result == null && xf != null)
        {
            result = xf.GetComponent(T);
            xf = xf.parent;
        }

        return result;
    }

    #endregion


    #region Instantiate

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="original"></param>
    /// <returns></returns>
    public static T Create<T>(T original)
        where T : Component
    {
        T result = ((GameObject)GameObject.Instantiate(original.gameObject)).GetComponent<T>();
        TrimClone(result.gameObject);
        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="original"></param>
    /// <returns></returns>
    public static GameObject Create(Object original)
    {
        GameObject result = (GameObject)GameObject.Instantiate(original);
        TrimClone(result);
        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="original"></param>
    /// <returns></returns>
    public static GameObject Create(string resource)
    {
        GameObject result = (GameObject)GameObject.Instantiate(Resources.Load(resource));
        TrimClone(result);
        return result;
    }

    #endregion


    #region Strings

    /// <summary>
    /// 
    /// </summary>
    /// <param name="obj"></param>
    public static void TrimClone(GameObject obj)
    {
        obj.name = obj.name.Substring(0, obj.name.Length - 7);
    }

    #endregion


    #region RPG

    /// <summary>
    /// Returns a factor that is equal to one if value is zero.
    /// The return value approaches zero as value approaches infinity.
    /// At a value of x, the result is y.
    /// </summary>
    /// <param name="restPoint"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static float DiminishingReturns(float x, float y, float value)
    {
        float xy = x * y;
        return xy / (xy + value * (1.0f - y));
    }


    /// <summary>
    /// Returns a factor that is equal to one if value is zero.
    /// The return value approaches zero as value approaches infinity.
    /// At a value of 100, the result is 0.5.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static float DiminishingReturns(float value)
    {
        return 100.0f / (100.0f + value);
    }

    #endregion


    #region Ancestors

    /// <summary>
    /// 
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static bool IsParentOf(this Transform a, Transform b)
    {
        do b = b.parent;
        while (a != b && b != null);

        return a == b;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static bool IsChildOf(this Transform a, Transform b) { return b.IsParentOf(a); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static bool IsParentOf(this GameObject a, GameObject b) { return a.transform.IsParentOf(b.transform); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static bool IsChildOf(this GameObject a, GameObject b) { return b.transform.IsParentOf(a.transform); }

    #endregion


    #region Mouse Location

    /// <summary>
    /// 
    /// </summary>
    /// <param name="Cam"></param>
    /// <param name="zValue"></param>
    /// <returns></returns>
    public static Vector3 MouseLocation2D(Camera Cam, float zValue)
    {
        Ray mouseRay = Cam.ScreenPointToRay(Input.mousePosition);
        float rayLength = (zValue - mouseRay.origin.z) / mouseRay.direction.z;
        return mouseRay.origin + mouseRay.direction * rayLength;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="zValue"></param>
    /// <returns></returns>
    public static Vector3 MouseLocation2D(float zValue) { return MouseLocation2D(Camera.main, zValue); }
    
    
    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public static Vector3 MouseLocation2D() { return MouseLocation2D(0.0f); }

    #endregion

}